/* EasyWay Game Engine
 * Copyright (C) 2006 Daniele Paggi.
 *  
 * Written by: 2006 Daniele Paggi<dshnt@hotmail.com>
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.easyway.quad;

import org.easyway.annotations.Optimized;
import org.easyway.interfaces.base.IPureLoopable;
import org.easyway.interfaces.base.IPureRender;
import org.easyway.interfaces.extended.IDrawing;
import org.easyway.interfaces.extended.ILayerID;
import org.easyway.interfaces.extended.ILoopable;
import org.easyway.interfaces.sprites.IPlain2D;
import org.easyway.objects.BaseObject;
import org.easyway.quad.lists.QuadLoopList;
import org.easyway.quad.lists.QuadRenderList;
import org.easyway.system.StaticRef;

/**
 * A quad manager is an Engine that speed-up your games whitout drawing what you
 * don't see<br>
 * note: if you use the QuadManager, your objects should not go on negative
 * locations (x<0 or y<0)
 */
@Optimized
public class QuadManager extends BaseObject implements ILoopable, IDrawing,
		ILayerID {

	/**
	 * generated version
	 */
	private static final long serialVersionUID = 516759724180319434L;

	/**
	 * the drawing sheet
	 */
	private int idLayer = -1;

	/** quad width */
	int widthQuad;

	/** quad height */
	int heightQuad;

	/** numero di quads */
	int numberQuadX, numberQuadY;

	/** grid of loop quads */
	// LoopQuad loopGrid[][];
	QuadLoopList loopGrid[][];

	/** grid of render quads */
	QuadRenderList renderGrid[][];

	/** indicates if use more memory or CPU */
	// TODO: DA RENDERE CAMBIABILE
	static boolean fullMemory = false;

	/**
	 * the default sizes of quad manager that are auto-used by the GameEngine to
	 * selfcreate the QuadManager
	 */
	public static int DEFAULT_WIDTH = 1000;

	public static int DEFAULT_HEIGHT = 1000;

	/**
	 * creates a new instance of QuadManger with the specified sizes
	 * 
	 * @param numberQuadX
	 *            number of "quads"
	 * @param numberQuadY
	 *            number of "quads"
	 */
	public QuadManager(int numberQuadX, int numberQuadY) {
		this.numberQuadX = numberQuadX;
		this.numberQuadY = numberQuadY;

		// diminuito di 1 poich� devono esserci SEMPRE 4 quad attivi a schermo
		this.widthQuad = StaticRef.getCamera().getWidth();
		this.heightQuad = StaticRef.getCamera().getHeight();

		loopGrid = new QuadLoopList[numberQuadX][numberQuadY];
		renderGrid = new QuadRenderList[numberQuadX][numberQuadY];

		if (fullMemory) {
			initializeFullyMemory();
		}

	}

	private void initializeFullyMemory() {
		for (int y = 0; y < numberQuadY; y++)
			for (int x = 0; x < numberQuadX; x++) {
				loopGrid[x][y] = new QuadLoopList();
				renderGrid[x][y] = new QuadRenderList();
			}
	}

	public void loop() {
		if (fullMemory) {
			int xstart = (int) StaticRef.getCamera().x / widthQuad - 2;
			int ystart = (int) StaticRef.getCamera().y / heightQuad - 2;
			for (int y = Math.max(ystart, 0); y <= Math.min(ystart + 5,
					numberQuadY - 1); ++y) {
				for (int x = Math.max(xstart, 0); x <= Math.min(xstart + 5,
						numberQuadX - 1); ++x) {
					loopGrid[x][y].loop();
				}
			}
		} else {
			int xstart = (int) StaticRef.getCamera().x / widthQuad - 2;
			int ystart = (int) StaticRef.getCamera().y / heightQuad - 2;
			for (int y = Math.max(ystart, 0); y <= Math.min(ystart + 5,
					numberQuadY - 1); ++y) {
				for (int x = Math.max(xstart, 0); x <= Math.min(xstart + 5,
						numberQuadX - 1); ++x) {
					if (loopGrid[x][y] != null)
						loopGrid[x][y].loop();
				}
			}
		}
	}

	public void render() {
		if (fullMemory) {
			int xstart = (int) StaticRef.getCamera().x / widthQuad - 2;
			int ystart = (int) StaticRef.getCamera().y / heightQuad - 2;
			for (int y = Math.max(ystart, 0); y <= Math.min(ystart + 5,
					numberQuadY - 1); ++y) {
				for (int x = Math.max(xstart, 0); x <= Math.min(xstart + 5,
						numberQuadX - 1); ++x) {
					renderGrid[x][y].render();
				}
			}
		} else {
			int xstart = (int) StaticRef.getCamera().x / widthQuad - 2;
			int ystart = (int) StaticRef.getCamera().y / heightQuad - 2;
			for (int y = Math.max(ystart, 0); y <= Math.min(ystart + 5,
					numberQuadY - 1); ++y) {
				for (int x = Math.max(xstart, 0); x <= Math.min(xstart + 5,
						numberQuadX - 1); ++x) {
					if (renderGrid[x][y] != null)
						renderGrid[x][y].render();
				}
			}
		}
	}

	/**
	 * adds a loopable object to the quad
	 */
	public void addLoop(IPlain2D plain) {
		int xstart = (int) plain.getX() / widthQuad;
		int ystart = (int) plain.getY() / heightQuad;
		if (!fullMemory) {
			if (loopGrid[xstart][ystart] == null)
				loopGrid[xstart][ystart] = new QuadLoopList();
		}
		loopGrid[xstart][ystart].add((IPureLoopable)plain);
	}

	/**
	 * removes a loopable object to the quad
	 */
	public void removeLoop(IPlain2D plain) {
		int xstart = (int) plain.getX() / widthQuad;
		int ystart = (int) plain.getY() / heightQuad;
		if (loopGrid[xstart][ystart] == null)
			System.out.println("ERRORE!" + xstart + " " + ystart);
		loopGrid[xstart][ystart].remove(plain);
	}

	/**
	 * adds a renderable objcts to the quad
	 */
	public void addRender(IPlain2D plain ) {
		int xstart = (int) plain.getX() / widthQuad;
		int ystart = (int) plain.getY() / heightQuad;
		if (!fullMemory) {
			if (renderGrid[xstart][ystart] == null)
				renderGrid[xstart][ystart] = new QuadRenderList();
		}
		renderGrid[xstart][ystart].add((IPureRender)plain);
	}

	/**
	 * removes a renderable object from the quad
	 */
	public void removeRender(IPlain2D plain) {
		int xstart = (int) plain.getX() / widthQuad;
		int ystart = (int) plain.getY() / heightQuad;
		renderGrid[xstart][ystart].remove(plain);
	}

	public int getLayer() {
		return 0;
	}

	/**
	 * cheks if the coordinates are in the same quad.<br>
	 * 
	 * @param x
	 *            actual coordinate
	 * @param y
	 *            actual coordainte
	 * @param oldx
	 *            old coordinate
	 * @param oldy
	 *            old coordinate
	 * @return true if the coordinates aren't in the same quad
	 */
	public boolean testUpdate(float x, float y, float oldx, float oldy) {
		return (((int) (x / widthQuad)) != ((int) (oldx / widthQuad)) || ((int) (y / heightQuad)) != ((int) (oldy / heightQuad)));
	}

	/**
	 * cheks if the coordinates are in the same quad.<br>
	 * 
	 * @param x
	 *            actual coordinate
	 * @param oldx
	 *            old coordinate
	 * @return true if the coordinates aren't in the same quad
	 */
	public boolean testUpdateX(float x, float oldx) {
		return ((int) (x / widthQuad)) != ((int) (oldx / widthQuad));
	}

	/**
	 * cheks if the coordinates are in the same quad.<br>
	 * 
	 * @param y
	 *            actual coordainte
	 * @param oldy
	 *            old coordinate
	 * @return true if the coordinates aren't in the same quad
	 */
	public boolean testUpdateY(float y, float oldy) {
		return ((int) (y / heightQuad)) != ((int) (oldy / heightQuad));
	}

	// -----------------------------------------------------------------
	// ---------------------------- IDLAYER-----------------------------
	// -----------------------------------------------------------------

	public int getIdLayer() {
		return idLayer;
	}

	public void setIdLayer(int id) {
		if (idLayer != -1) {
			StaticRef.layers[idLayer].remove(this);
		}
		if (id < 0) {
			id = 0;
		} else if (id > StaticRef.layers.length) {
			id = StaticRef.layers.length;
		}
		idLayer = id;
		StaticRef.layers[idLayer].add(this);
	}

}
